import base64
import pickle

from django.shortcuts import render

# Create your views here.
from django_redis import get_redis_connection
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

from carts import constants
from carts.serializers import CartSerializer, CartSKUSerializer, CartDeleteSerializer
from goods.models import SKU


class CartsListView(APIView):
    """购物车"""

    def perform_authentication(self, request):
        """重写方法  不在一开始就校验jwt
        如果用户未登录,在前端发送请求的时候携带了undefined的jwt到后端,会进入系统的这个方法,系统会报404
        """
        pass

    def post(self, request):
        """保存商品到购物车
        1.接收校验参数   sku_id  count  selected
        2.判断用户是否登录  如果已经登录  保存到redis  如果没有登录 保存到cookie
        3.序列化返回结果
        """
        serializer = CartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # 从已校验数据中获取参数
        sku_id = serializer.validated_data['sku_id']
        count = serializer.validated_data['count']
        selected = serializer.validated_data['selected']
        # 在进行判断用户是否登录之前,需要修改一下系统自带的jwt认证方法,否则在没有登录而前端携带了jwt头的时候发起请求
        # 后端就会报错
        try:
            user = request.user
        except Exception:
            user = None
        # 如果用户存在并且认证通过 就执行往redis中保存的操作
        if user is not None and user.is_authenticated:
            # 获取redis连接
            redis_conn = get_redis_connection('cart')
            pl = redis_conn.pipeline()
            # hincrby为hash类型数据的value(sku_id)加上增量count,如果skuid不存在 初始化为0
            pl.hincrby('carts_%s' % user.id, sku_id, count)
            # 将商品勾选状态保存到set中
            if selected:
                pl.sadd('cart_selected_%s' % user.id, sku_id)
            pl.execute()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            # 尝试从cookie中获取已经存在的购物车数据
            cart = request.COOKIES.get('cart')
            if cart is not None:
                # 取出来的是字符串数据  将之转成bytes之后给b64解码成二进制的元数据
                bytes_cart = base64.b64decode(cart.encode())
                # 使用pickle将bytes解码成字典(因为存的时候使用的是字典编码成2进制)
                cart_dict = pickle.loads(bytes_cart)
            else:
                cart_dict = {}

            sku = cart_dict.get(sku_id)  # sku_id:{count: ,selected:}
            if sku:  # {count: ,selected:}
                count += int(sku['count'])

            # 设置值
            cart_dict[sku_id] = {
                "count": count,
                "selected": selected
            }

            # 将字典数据进行处理后设置到cookie中进行返回   转成二进制  b64编码  解码程字符串
            cookie_cart = base64.b64encode(pickle.dumps(cart_dict)).decode()
            # 生成响应对象
            resp = Response(serializer.data, status=status.HTTP_201_CREATED)
            # 设置cookie
            resp.set_cookie('cart', cookie_cart, max_age=constants.CART_COOKIE_EXPIRES)
            return resp

    def get(self, request):
        """获取购物车页面数据
        1.获取参数  校验 判断用户是否登录
        2.如果未登录 从cookie中取数据 查询数据库 返回数据
        3.如果已登录 从redis中
        """
        try:
            user = request.user
        except Exception:
            user = None

        if user is not None and user.is_authenticated():
            redis_conn = get_redis_connection('cart')
            # carts的商品和数量
            # hgetall返回的数据格式{15:3,16:1}  键值对分别是商品id和数量 不过是二进制格式的字符串
            cart_redis = redis_conn.hgetall('carts_%s' % user.id)
            # 勾选状态
            cart_redis_selected = redis_conn.smembers('cart_selected_%s' % user.id)
            cart = {}
            # 遍历redis的cart  生成商品对应数量及勾选状态的字典{14:{se:xx,cou:xx}}
            for sku_id, count in cart_redis.items():
                cart[int(sku_id)] = {
                    'selected': sku_id in cart_redis_selected,
                    'count': count
                }
        else:
            try:
                cart_cookie = request.COOKIES.get('cart')
                cart = pickle.loads(base64.b64decode(cart_cookie.encode()))  # ->dict
            except Exception:
                cart = {}

        # 从数据库中查询数据 并序列化返回
        cart_id_list = cart.keys()
        skus = SKU.objects.filter(id__in=cart_id_list)
        for sku in skus:
            # 对于从数据库中查询出的sku  加上count和selected属性 (对应购物车中的数据)
            # 动态语言 可以给对象添加属性
            sku.count = cart[sku.id]['count']
            sku.selected = cart[sku.id]['selected']
        sl = CartSKUSerializer(skus, many=True)
        return Response(sl.data)

    def put(self, request):
        """购物车页面数据修改实现
        1.获取 校验数据
        2.查询购物车
        3.写购物车
        4.返回
        """
        # 序列化器校验
        sl = CartSerializer(data=request.data)
        sl.is_valid(raise_exception=True)
        # 注意要在valid之后才能获取到validated_data中的数据
        count = sl.validated_data.get('count')
        selected = sl.validated_data.get('selected')
        sku_id = sl.validated_data.get('sku_id')

        try:
            user = request.user
        except Exception:
            user = None
        if user is not None and user.is_authenticated():
            redis_conn = get_redis_connection('cart')
            # 因为是修改操作,所以不需要关心redis中是否有对应的数据
            pl = redis_conn.pipeline()
            # 设置商品对应的hash
            pl.hset('carts_%s' % user.id, sku_id, count)
            # 如果勾选了  添加勾选  否则移除勾选  (不需要关心之前是否已经勾选,因为集合会去重)
            if selected:
                pl.sadd('cart_selected_%s' % user.id, sku_id)
            else:
                pl.srem('cart_selected_%s' % user.id, sku_id)
            pl.execute()
            return Response(sl.data, status=status.HTTP_200_OK)
        else:
            # 用户未登录，在cookie中保存
            # 使用pickle序列化购物车数据，pickle操作的是bytes类型
            cart = request.COOKIES.get('cart')
            if cart is not None:
                cart = pickle.loads(base64.b64decode(cart.encode()))
            else:
                cart = {}
            # 设置购物车
            cart[sku_id] = {
                'count': count, 'selected': selected
            }
            cart = base64.b64encode(pickle.dumps(cart)).decode()
            resp = Response(sl.data)
            resp.set_cookie('cart', cart, max_age=constants.CART_COOKIE_EXPIRES)
            return resp

    def delete(self, request):
        """删除购物车数据
        """

        serializer = CartDeleteSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        sku_id = serializer.validated_data['sku_id']

        try:
            user = request.user
        except Exception:
            # 验证失败，用户未登录
            user = None

        if user is not None and user.is_authenticated:
            # 用户已登录，在redis中保存
            redis_conn = get_redis_connection('cart')
            pl = redis_conn.pipeline()
            pl.hdel('carts_%s' % user.id, sku_id)
            pl.srem('cart_selected_%s' % user.id, sku_id)
            pl.execute()
            return Response(status=status.HTTP_204_NO_CONTENT)
        else:
            # 用户未登录，在cookie中保存
            response = Response(status=status.HTTP_204_NO_CONTENT)

            # 使用pickle序列化购物车数据，pickle操作的是bytes类型
            cart = request.COOKIES.get('cart')
            if cart is not None:
                cart = pickle.loads(base64.b64decode(cart.encode()))
                if sku_id in cart:
                    del cart[sku_id]
                    cookie_cart = base64.b64encode(pickle.dumps(cart)).decode()
                    # 设置购物车的cookie
                    # 需要设置有效期，否则是临时cookie
                    response.set_cookie('cart', cookie_cart, max_age=constants.CART_COOKIE_EXPIRES)
            return response


class CartViewSet():
    """获取 校验数据  进行业务处理
    1.使用序列化器进行数据校验
    2.将数据转换格式之后以字符串格式存入redis或者cookie
    3.
    """
    pass
